//
//  GAKKINetworkManager.m
//  GANGAIKit
//
//  Created by fh on 2019/11/11.
//  Copyright © 2019 cashchina. All rights reserved.
//

#import "GAKKINetworkManager.h"
#import <CommonCrypto/CommonDigest.h>
#import <AFNetworking/AFNetworking.h>
#import <YYModel/YYModel.h>
#import "GAKKIHUD.h"
#import "GAKKIConfig.h"
#import "GAKKINetworkResponseBaseModel.h"
#import "GAKKINetworkCommonRequestModel.h"
#import "GAKKINetworkLogger.h"

/// 响应数据key
NSString *const GAKKINetworkManagerResponseDataKey = @"GAKKINetworkManagerResponseDataKey";
/// 默认显示文本
NSString *const GAKKINetworkManagerDefaultRemindText = @"加载中...";

/// 登录通知
NSString *const GAKKILoginSuccessNotification = @"GAKKILoginNotification";
/// 登出通知
NSString *const GAKKILogoutSuccessNotification = @"GAKKILogoutNotification";

/// 初始请求URL key
NSString *const GAKKINetworkLoggerOriginRequestURLKey = @"GAKKINetworkLoggerOriginRequestURLKey";
/// 初始请求参数 key
NSString *const GAKKINetworkLoggerOriginRequestParamsKey = @"GAKKINetworkLoggerOriginRequestParamsKey";
/// 最后的请求参数 key
NSString *const GAKKINetworkLoggerActualRequestParamsKey = @"GAKKINetworkLoggerActualRequestParamsKey";


@interface GAKKINetworkManager ()
/** 任务队列 */
@property (nonatomic, readwrite, strong) NSMutableDictionary<NSString*, NSURLSessionDataTask*> *taskDic;
/** 打印日志 */
@property (nonatomic, strong) GAKKINetworkLogger *logger;
@end

@implementation GAKKINetworkManager

#pragma mark - Public
/// 单例对象
+ (instancetype)sharedInstance {
    static GAKKINetworkManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc] init];
    });
    return manager;
}

/// 全参数请求方法:更底层,扩展性最大
/// @param requestMethod 请求方式
/// @param innerUrl 请求链接:返回GAKKINetworkApi实力对象的属性,属性即后台的接口,详情见GAKKINetworkApi的实现
/// @param shouldAutoAddIndicator 是否自动加载指示器,NO的话请求时的提示文本remindText参数无效
/// @param remindText 请求时的提示文本
/// @param parameters 请求参数
/// @param showFullResponseData 是否应该返回无处理的服务器数据,NO的话内部会处理服务器数据,提取出dto字典里的内容
/// @param success 成功回调
/// @param failure 失败回调
+ (void)baseWholeParameterRequestWithMethod:(RequestMethod)requestMethod innerUrl:(NSString * _Nullable (^)(GAKKINetworkApi * _Nullable))innerUrl addIndicator:(BOOL)shouldAutoAddIndicator remindText:(NSString *)remindText parameters:(NSDictionary *)parameters showFullResponseData:(BOOL)showFullResponseData success:(void (^)(NSDictionary * _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure {
    
    NSString *urlApi = nil;
    if (innerUrl) {
        urlApi = innerUrl([GAKKINetworkApi api]);
    }
    if (kStringIsEmpty(urlApi)) {
        if (shouldAutoAddIndicator) {
            [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:@"请求url为空"];
            return;
        }
    }
    [[GAKKINetworkManager sharedInstance] coreRequestWithRequestMethod:requestMethod url:urlApi parameters:parameters addIndicator:shouldAutoAddIndicator remindText:remindText showFullResponseData:showFullResponseData success:success failure:failure];
}

+ (void)getRequestWithInnerUrlStr:(NSString * _Nullable (^)(GAKKINetworkApi * _Nullable))innerUrl remindText:(NSString *)remindText parameters:(NSDictionary *)parameters success:(void (^)(NSDictionary * _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure {
    [self baseWholeParameterRequestWithMethod:RequestMethodGet innerUrl:innerUrl addIndicator:YES remindText:remindText parameters:parameters showFullResponseData:NO success:success failure:failure];
}

+ (void)postRequestWithInnerUrlStr:(NSString * _Nullable (^)(GAKKINetworkApi * _Nullable))innerUrl remindText:(NSString *)remindText parameters:(NSDictionary *)parameters success:(void (^)(NSDictionary * _Nonnull))success failure:(void (^)(NSError * _Nonnull))failure {
    [self baseWholeParameterRequestWithMethod:RequestMethodPost innerUrl:innerUrl addIndicator:YES remindText:remindText parameters:parameters showFullResponseData:NO success:success failure:failure];
}

/// 取消请求任务
/// @param urlStr 请求URL
+ (void)cancleTaskWithURLStr:(NSString *)urlStr {
    NSString *key = [[GAKKINetworkManager sharedInstance] cachedTaskNameForUrl:urlStr];
    NSURLSessionDataTask *task = [[GAKKINetworkManager sharedInstance].taskDic objectForKey:key];
    [task cancel];
    [[GAKKINetworkManager sharedInstance].taskDic removeObjectForKey:key];
}

#pragma mark - Private
- (void)coreRequestWithRequestMethod:(RequestMethod)method url:(NSString *)url parameters:(NSDictionary *)parameters addIndicator:(BOOL)shouldAutoAddIndicator remindText:(NSString *)remindText showFullResponseData:(BOOL)shouldShowFullResponseData success:(void (^)(NSDictionary *resultDic))success failure:(void (^)(NSError *error))failure {
    
    if (shouldAutoAddIndicator) {
        if (kStringIsNotEmpty(remindText)) {
            [[GAKKIHUD shareGAKKIHUD] showLoadingViewWithInfo:kEmptyString(remindText)];
        }
    }
    // 这个字典是为了方便打印网络请求的一些信息,最后通过这个字典去传递参数
    NSMutableDictionary *gakkiAllParametersDic = [NSMutableDictionary dictionary];
    gakkiAllParametersDic[GAKKINetworkLoggerOriginRequestURLKey] = url;
    gakkiAllParametersDic[GAKKINetworkLoggerOriginRequestParamsKey] = parameters;
    // 这里需要添加处理公共参数:根据业务替换掉@{}
    NSDictionary *commonDic = @{};
    NSMutableDictionary *parametersDic = [NSMutableDictionary dictionaryWithDictionary:commonDic];
    if (parameters) {
        [parametersDic setValuesForKeysWithDictionary:parameters];
    }
    gakkiAllParametersDic[GAKKINetworkLoggerActualRequestParamsKey] = parametersDic;
    
    NSString *requestMethodStr = @"POST";
    switch (method) {
        case RequestMethodGet:
            requestMethodStr = @"GET";
            break;
        case RequestMethodPost:
            requestMethodStr = @"POST";
            break;
    }
    
    [self commonRequestWithMethod:requestMethodStr forURL:url parameters:gakkiAllParametersDic success:^(NSDictionary *responseDic) {
        if (shouldAutoAddIndicator) {
            [[GAKKIHUD shareGAKKIHUD] hiddenLoadingView];
        }
        // 这里是为了处理一些后台返回数据格式不一致的问题
        if (shouldShowFullResponseData) {
            // 直接传递完整数据,这个后面要考虑需不需要封装处理登录超时的情况
            if (success) {
                success(responseDic);
            }
        }else {
            // 处理掉公共部分之后再传递出去
            GAKKINetworkResponseBaseModel *resultModel = [GAKKINetworkResponseBaseModel yy_modelWithJSON:responseDic];
            // 把原数据保存起来，用于状态判断，因为后台返回的格式很混乱，不能保证返回固定的字段
            resultModel.originData = responseDic;
            
            if (resultModel.status == GAKKINetServerStatusSuccess) {
                
                if (resultModel.dataStatus == GAKKINetServerDataStatusCorrect) {
                    if (success) {
                        // 这里要根据后台返回的格式重新处理一下,返回非公共部分的数据
                        success(responseDic);
                    }
                }else {
                    if (resultModel.dataStatus == GAKKINetServerDataStatusNull) {
                        if (shouldAutoAddIndicator) {
                            [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:@"获取数据失败，请稍后再试！"];
                        }
                    }else if (resultModel.dataStatus == GAKKINetServerDataStatusWrong){
                        if (shouldAutoAddIndicator) {
                            [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:@"服务器数据类型有误"];
                        }
                    }
                }
                
            }else if (resultModel.status == GAKKINetServerStatusFailed) {
                
                if (shouldAutoAddIndicator) {
                    [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:@"后台返回的错误信息"];
                }
                
                if (failure) {
                    failure([NSError errorWithDomain:GAKKIServerErrorDomain code:resultModel.status userInfo:@{NSLocalizedDescriptionKey : kEmptyString(@"后台返回的错误信息")}]);
                }
                /// 登录过期发通知弹出登录页面
                if (resultModel.needLogin == YES) {
                    // 需要清楚登陆信息
                    [[NSNotificationCenter defaultCenter] postNotificationName:GAKKILogoutSuccessNotification object:nil];
                }
                
            }else if (resultModel.status == GAKKINetServerStatusOtherError) {
                if (shouldAutoAddIndicator) {
                    [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:@"其他错误"];
                }
            }
        }
        
    } failure:^(NSError *error) {
        if (shouldAutoAddIndicator) {
            [[GAKKIHUD shareGAKKIHUD] hiddenLoadingView];
            [[GAKKIHUD shareGAKKIHUD] showTipViewWithInfo:error.localizedDescription];
        }
        if (failure) {
            failure(error);
        }
    }];
    
}

/// 开启请求任务并且添加到请求任务数组里
/// @param method 请求方式
/// @param url 请求URL
/// @param parameters 请求参数
/// @param success 请求成功回调
/// @param failure 请求失败回调
- (void)commonRequestWithMethod:(NSString *)method forURL:(NSString *)url parameters:(id)parameters success:(void(^)(NSDictionary *responseDic))success failure:(void(^)(NSError *error))failure {
   
    __weak typeof(self) weakSelf = self;
    NSURLSessionDataTask *task = [self createRequestTaskWithMethod:method forURL:url parameters:parameters success:^(NSString *urlStr, NSDictionary *responseDic) {
        [weakSelf.taskDic removeObjectForKey:[weakSelf cachedTaskNameForUrl:urlStr]];
        if (success) {
            success(responseDic);
        }
    } failure:^(NSString *urlStr, NSError *error) {
        [weakSelf.taskDic removeObjectForKey:[weakSelf cachedTaskNameForUrl:urlStr]];
        if (failure) {
            failure(error);
        }
    }];
    [self.taskDic setObject:task forKey:[self cachedTaskNameForUrl:url]];
    [task resume];
}

/// 创建请求任务:不开启请求任务
/// @param method 请求方式
/// @param url 请求URL
/// @param parameters 请求参数
/// @param success 请求成功回调
/// @param failure 请求失败回调
- (NSURLSessionDataTask *)createRequestTaskWithMethod:(NSString *)method forURL:(NSString *)url parameters:(id)parameters success:(void(^)(NSString *urlStr, NSDictionary *responseDic))success failure:(void(^)(NSString *urlStr, NSError *error))failure {

    if (![url hasPrefix:@"http"]) {
#ifdef DEBUG
        DBLog(@"网络请求链接格式不对");
#endif
        return nil;
    }
    AFHTTPSessionManager *manager = [self sharedHttpSessionManager];
    /// 处理请求参数为最终的请求参数
    id realLastParameters = [self handleRequestParametersWithGakkiAllParametersDic:parameters];
    
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:url relativeToURL:manager.baseURL] absoluteString] parameters:realLastParameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            dispatch_async(manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (failure) {
                    failure(url,serializationError);
                }
            });
        }
        return nil;
    }
    
    /// 设置请求头部
    [self configRequestHeaderFieldsWithRequest:request];
    /// 配置请求解析
    [self configRequestSerializerWith:manager];
    /// 配置响应解析
    [self configResponseSerializerWith:manager];
    
    NSMutableDictionary *allUnHandleParameters = parameters;
    if (self.logger.shouldPrintNetLog) {
        if (!self.logger.easyPrintNetLog) {
            [self.logger logDebugInfoWithRequest:request apiName:allUnHandleParameters[GAKKINetworkLoggerOriginRequestURLKey] originRequestParams:allUnHandleParameters[GAKKINetworkLoggerOriginRequestParamsKey] actualRequestParams:allUnHandleParameters[GAKKINetworkLoggerActualRequestParamsKey]];
        }
    }
    
    __weak typeof(self) weakSelf = self;
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        
        if (weakSelf.logger.shouldPrintNetLog) {
            if (!weakSelf.logger.easyPrintNetLog) {
                if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                    [weakSelf.logger logDebugInfoWithResponse:(NSHTTPURLResponse *)response responseObject:responseObject responseString:[responseObject yy_modelToJSONString] request:request originRequestParams:allUnHandleParameters[GAKKINetworkLoggerOriginRequestParamsKey] actualRequestParams:allUnHandleParameters[GAKKINetworkLoggerActualRequestParamsKey] error:error];
                }else {
                    [weakSelf.logger logDebugInfoWithResponse:nil responseObject:responseObject responseString:[responseObject yy_modelToJSONString] request:request originRequestParams:allUnHandleParameters[GAKKINetworkLoggerOriginRequestParamsKey] actualRequestParams:allUnHandleParameters[GAKKINetworkLoggerActualRequestParamsKey] error:error];
                }
            }
        }
        
        //Bug Fixed: 使用response.URL.absoluteString会自动将http更换成https请求，这里需要保证跟原先请求的url一致,所以使用task.originalRequest.URL.absoluteString
        if (error) {
//            #ifdef DEBUG
//                DBLog(@"网络请求API: %@\n网络请求参数:\n%@\n网络请求响应数据:\n%@\n网络请求响应错误信息:\n%@\n",url,parameters,[responseObject yy_modelDescription],error);
//            #endif
            if (weakSelf.logger.shouldPrintNetLog) {
                if (weakSelf.logger.easyPrintNetLog) {
                    [weakSelf.logger easyLogDebugInfoWithUrl:url originRequestParams:allUnHandleParameters[GAKKINetworkLoggerOriginRequestParamsKey] actualRequestParams:allUnHandleParameters[GAKKINetworkLoggerActualRequestParamsKey] responseObject:responseObject error:error];
                }
            }
            if (failure) {
                failure(dataTask.originalRequest.URL.absoluteString,error);
            }
        }else {
            
            NSMutableDictionary *dataDic = [NSMutableDictionary dictionary];
            if ([responseObject isKindOfClass:[NSDictionary class]]) {
                dataDic[GAKKINetworkManagerResponseDataKey] = responseObject;
            }else if ([responseObject isKindOfClass:[NSData class]]) {
                id handleData = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil];
                // 如果不是json试着转化为string
                if (!handleData) {
                    handleData = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
                }
                dataDic[GAKKINetworkManagerResponseDataKey] = handleData;
            }else if ([responseObject isKindOfClass:[NSString class]]) {
                dataDic[GAKKINetworkManagerResponseDataKey] = responseObject;
            }else {
                if (failure) {
                    failure(dataTask.originalRequest.URL.absoluteString,[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"响应数据类型有误"}]);
                }
                return;
            }
//            #ifdef DEBUG
//                DBLog(@"网络请求API: %@\n网络请求参数:\n%@\n网络请求响应数据:\n%@\n网络请求响应错误信息:\n%@\n",url,parameters,[dataDic yy_modelDescription],error);
//            #endif
            if (weakSelf.logger.shouldPrintNetLog) {
                if (weakSelf.logger.easyPrintNetLog) {
                    [weakSelf.logger easyLogDebugInfoWithUrl:url originRequestParams:allUnHandleParameters[GAKKINetworkLoggerOriginRequestParamsKey] actualRequestParams:allUnHandleParameters[GAKKINetworkLoggerActualRequestParamsKey] responseObject:dataDic error:error];
                }
            }
            if (success) {
                success(dataTask.originalRequest.URL.absoluteString,dataDic);
            }
        }
        
    }];
    return dataTask;
}

/// 配置请求头
/// @param request 请求
- (void)configRequestHeaderFieldsWithRequest:(NSMutableURLRequest *)request {
    /// 设置请求头部
    NSMutableDictionary *tempHeaderDic = [NSMutableDictionary dictionaryWithDictionary:request.allHTTPHeaderFields];
    request.allHTTPHeaderFields = tempHeaderDic;
}

/// 配置请求参数
/// @param gakkiAllParametersDic 未处理的请求参数
- (id)handleRequestParametersWithGakkiAllParametersDic:(NSDictionary *)gakkiAllParametersDic {
    NSMutableDictionary *dic = gakkiAllParametersDic[GAKKINetworkLoggerActualRequestParamsKey];
    return dic;
}

/// 配置请求解析
/// @param manager 管理类
- (void)configRequestSerializerWith:(AFHTTPSessionManager *)manager {
    // 请求超时时间45s
    manager.requestSerializer.timeoutInterval = 45.f;
    [manager.requestSerializer setValue:@"application/x-www-form-urlencoded;charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
}

/// 配置响应解析
/// @param manager 管理类
- (void)configResponseSerializerWith:(AFHTTPSessionManager *)manager {
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"text/json", @"application/json",@"text/javascript", @"text/plain", nil];
}

/// 将请求链接进行MD5加密转化为key
/// @param urlStr 请求链接
- (nullable NSString *)cachedTaskNameForUrl:(nullable NSString *)urlStr {
    const char *str = urlStr.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], [urlStr.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", urlStr.pathExtension]];
    
    return filename;
}

/// AF请求管理单例
- (AFHTTPSessionManager *)sharedHttpSessionManager {
    static dispatch_once_t onceToken;
    static AFHTTPSessionManager *manager;
    dispatch_once(&onceToken, ^{
        manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
        // 支持https（校验证书),allowInvalidCertificates是否允许无效证书（也就是自建的证书,默认为NO,如果是需要验证自建证书,需要设置为YES
        securityPolicy.allowInvalidCertificates = NO;
        // validatesDomainName 是否需要验证域名，默认为YES；
        [securityPolicy setValidatesDomainName:NO];
        manager.securityPolicy = securityPolicy;
        manager.operationQueue.maxConcurrentOperationCount = 8;
        // DBLog(@"%ld",manager.operationQueue.maxConcurrentOperationCount);
    });
    return manager;
}

- (GAKKINetworkLogger *)logger {
    if (!_logger) {
        _logger = [[GAKKINetworkLogger alloc] init];
#ifdef DEBUG
        _logger.shouldPrintNetLog = YES;
        _logger.easyPrintNetLog = YES;
#endif
    }
    return _logger;
}

@end
